import { SQLite, SQLiteObject } from '@ionic-native/sqlite';
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import GLCatalog, { GLItem, GLList, GLLink, GLContent } from './GLCatalog';
import 'rxjs/add/operator/toPromise';
import { File } from '@ionic-native/file';
import JSZip from 'jszip';
import JSZipUtils from 'jszip-utils';
import Q from 'q';
import { AlertController, LoadingController, PopoverController } from 'ionic-angular';
import querystring from 'querystring';
import ProgressPage from '../progress.component';

const DEV = true;

const BASE_URI = DEV ? "/api4/" : "http://edge.ldscdn.org/mobile/gospelstudy/production/v3/";

if(DEV) {
	Q.longStackSupport = true;
}

@Injectable()
export default class GLCatalogProvider4 {
	private promise: PromiseLike<GLCatalog>;
	constructor(private http: Http, private sqlite: SQLite, private fs: File, private alertCtl: AlertController, private loadingCtl: LoadingController, private popoverCtl: PopoverController) {
		this.promise = (() => {
			if(!("glCatalogVersion" in localStorage)) {
				return this.updateCatalog();
			}
			return Q.resolve();
		})()
		.then(() => {
			return this.sqlite.create({
				name: 'catalog.sqlite',
				'location': 'default'
			});
		})
		.then((db: SQLiteObject) => {
			return new GLCatalog4(db, sqlite, fs, alertCtl);
		})
		.catch(console.error);
	}

	private updateCatalog(): PromiseLike<void> {
		let progressCallback = info=>{};

		let loading = this.popoverCtl.create(ProgressPage, {
			setCallback: (cb) => progressCallback = cb,
				title: "Downloading catalog..."
		}, {
			enableBackdropDismiss: false
		});
		loading.present();

		return this.http.get(BASE_URI+"index.json")
		.toPromise()
		.then((data): PromiseLike<void> => {
			let newVersion = data.json().catalogVersion;
			console.log("loading catalog "+newVersion);
			return new Promise((resolve, reject) => {
				JSZipUtils.getBinaryContent(BASE_URI+"catalogs/"+newVersion+".zip", (err, data: any) => {
					if(err) {
						reject(err);
						return;
					}
					resolve(data);
				}, (info) => progressCallback(info));
			})
			.then((resp): PromiseLike<void> => {
				console.log("loaded");
				console.log(resp);
				var zip = new JSZip();
				return zip.loadAsync(resp)
				.then(() => {
					return zip.files["Catalog.sqlite"].async("blob");
				});
			})
			.then((data: Blob) => {
				console.log(data);
				return this.fs.writeFile(this.fs.applicationStorageDirectory, "databases/catalog.sqlite", data, {
					replace: true
				});
			})
			.then((): void => {
				localStorage.glCatalogVersion = newVersion;
				loading.dismiss();
			});
		});
	}

	getCatalog(): PromiseLike<GLCatalog> {
		return this.promise;
	}
}

class GLCatalog4 extends GLCatalog {
	private dbCache = {};
	constructor(private db: SQLiteObject, private sqlite: SQLite, private fs: File, private alertCtl: AlertController) {
		super();
		db.executeSql("SELECT * FROM item WHERE uri='/scriptures/ot'", {})
		.then((data) => {
			console.log(data);
		});
	}
	private confirm(text: string): Promise<boolean> {
		return new Promise((resolve, reject) => {
			let dialog = this.alertCtl.create({
				title: "Confirm download",
				message: text,
				buttons: [
					{
						text: "No",
						role: "cancel",
						handler: () => {
							resolve(false);
						}
					},
					{
						text: "Yes",
						handler: () => {
							resolve(true);
						}
					}
				]
			});
			dialog.present();
		});
	}
	getItem(path: string): Promise<GLItem> {
		console.log("getting "+path);
		let ind = path.indexOf("?");
		let data: any = {};
		if(ind > -1) {
			data = querystring.parse(path.substring(ind+1));
			path = path.substring(0, ind);
		}
		if(path == "/") {
			return this.getRoot();
		}
		else if(path.indexOf("/category/") === 0) {
			return this.db.executeSql("SELECT name FROM item_category WHERE _id=?", [path.substring(10)])
			.then((result) => {
				return result.rows.item(0).name;
			})
			.then((name) => {
				return this.db.executeSql("SELECT uri, title FROM item WHERE item_category_id=? AND language_id=?",[path.substring(10), 1])
				.then((result) => {
					let items = [];
					console.log("got result", result);
					for(let i = 0; i < result.rows.length; i++) {
						let item = result.rows.item(i);
						items.push(new GLLink(item.title, item.uri));
					}
					console.log("returning");
					return new GLList(name, items, path);
				});
			});
		}
		else if(path.indexOf("/refs") === 0) {
			let basePage = path.substring(5);
			return this.getDatabase(basePage, this.confirm.bind(this))
			.then((res) => {
				return res.db.executeSql("SELECT content_html FROM related_content_item WHERE subitem_id=(SELECT _id FROM subitem WHERE uri=?) ORDER BY byte_location", [basePage]);
			})
			.then((result: any) => {
				let content = "";
				for(var i = 0; i < result.rows.length; i++) {
					content += result.rows.item(i).content_html;
				}
				return new GLContent("References", content, path, data.verse);
			});
		}
		else if(path.indexOf("/hymn/") === 0) {
			let hymnNo = path.substring(6);
			return this.getDatabase("/manual/hymns", this.confirm.bind(this))
			.then((res) => {
				return res.db.executeSql("SELECT title, uri, (SELECT content_html FROM subitem_content WHERE subitem_id=subitem._id) AS content FROM subitem WHERE _id=?", [parseInt(hymnNo)+4]);
			})
			.then((result: any) => {
				let row = result.rows.item(0);
				return new GLContent(row.title, row.content, row.uri);
			});
		}
		console.log(path, path.indexOf("/refs"));
		return this.getDatabase(path, this.confirm.bind(this))
		.then((res) : Promise<GLItem> => {
			console.log(res);
			console.log(res.uri, path);
			console.log("hoi");
			if(res.uri == path) {
				return res.db.executeSql("SELECT uri, title_html AS title FROM nav_collection WHERE nav_section_id IS NOT NULL ORDER BY position ASC", [])
				.then((result) => {
					if(result.rows.length < 1) {
						return res.db.executeSql("SELECT uri, title_html AS title FROM nav_item", []);
					}
					return result;
				})
				.then((result) => {
					let items = [];
					for(let i = 0; i < result.rows.length; i++) {
						let item = result.rows.item(i);
						items.push(new GLLink(item.title, item.uri));
					}
					return this.db.executeSql("SELECT title FROM item WHERE uri=? AND language_id=?", [res.uri, 1])
					.then((result): GLItem => {
						return new GLList(result.rows.item(0).title, items, path);
					});
				});
			}
			return res.db.executeSql("SELECT title, (SELECT content_html FROM subitem_content WHERE subitem_id=subitem._id) AS content FROM subitem WHERE uri=?", [path])
			.then((result) => {
				if(result.rows.length < 1) {
					return res.db.executeSql("SELECT nav_collection.title_html AS root_title, nav_item.uri, nav_item.title_html AS title FROM nav_item JOIN nav_collection ON nav_collection._id = nav_item.nav_section_id WHERE nav_collection.uri = ?", [path])
					.then((result) => {
						let items = [];
						for(let i = 0; i < result.rows.length; i++) {
							let item = result.rows.item(i);
							items.push(new GLLink(item.title, item.uri));
						}
						return new GLList(result.rows.item(0).root_title, items, path);
					});
				}
				let item = result.rows.item(0);
				let focus = data.context || data.verse;
				console.log(data);
				return new GLContent(item.title, item.content, path, focus);
			});
		});
	}
	getRoot(): Promise<GLItem> {
		console.log("getting root");
		return this.db.executeSql("SELECT _id, name"/*, (SELECT cover_renditions FROM library_collection WHERE title_html=name) AS cover_renditions"*/+" FROM item_category", {})
		.then((data) => {
			console.log(data);
			console.log("got data");
			let items = [];
			for(var i = 0; i < data.rows.length; i++) {
				let item = data.rows.item(i);
				items.push(new GLLink(item.name, "/category/"+item._id));
			}
			return new GLList("Library", items, "/");
		});
	}
	private getDatabase(uri: string, confirm?: (text:string)=>Promise<boolean>): Promise<DBInfo> {
		console.log("DB request for "+uri);
		let spl = uri.split(/[\/#]+/);
		let i = spl.length;
		let uris = new Array<string>();
		while(i > 0) {
			let basePath = spl.slice(0, i).join("/");
			uris.push(basePath);
			console.log("checking "+basePath);
			if(basePath in this.dbCache) {
				return Promise.resolve({db: this.dbCache[basePath], uri: basePath});
			}
			console.log("failed check");
			i--;
		}
		console.log("not in cache");
		return this.loadDatabase(uris, confirm)
		.then((res) => {
			console.log("loaded db");
			this.dbCache[res.uri] = res.db;
			return res;
		});
	}
	private loadDatabase(uris: string[], confirm?: (text:string)=>Promise<boolean>): Promise<DBInfo> {
		console.log("loadDatabase", uris);
		return this.db.executeSql("SELECT external_id, title, uri, version FROM item WHERE uri IN ("+(Array(uris.length).fill("?").join())+") AND language_id=?", [...uris, 1])
		.then((result) => {
			console.log(result);
			let file = result.rows.item(0).external_id;
			let uri = result.rows.item(0).uri;
			let version = result.rows.item(0).version;
			let title = result.rows.item(0).title;
			return this.fs.checkFile(this.fs.applicationStorageDirectory, "databases/"+file+".sqlite")
			.then(()=>{}, () => {
				// not found, ask to download
				return (() => {
					if(confirm) {
						return confirm("Do you want to download \""+title+"\"?")
						.then((confirmed) => {
							if(!confirmed) {
								throw "declined";
							}
						});
					}
					return Promise.resolve();
				})()
				.then(() => {
					return Q.ninvoke(JSZipUtils, "getBinaryContent", BASE_URI+"item-packages/"+file+"/"+version+".zip")
					.then((data) => {
						var zip = new JSZip();
						return zip.loadAsync(data)
						.then(() => {
							console.log(zip.files);
							return zip.files["package.sqlite"].async("blob");
						});
					})
					.then((data) => {
						return this.fs.writeFile(this.fs.applicationStorageDirectory, "databases/"+file+".sqlite", data, {replace: true});
					});
				});
			})
			.then(() => {
				console.log(uri, "roar");
				return this.sqlite.create({
					name: file+".sqlite",
					'location': 'default'
				})
				.then((db) => {return {uri, db}});
			})
		});
	}
}

interface DBInfo {
	uri: string;
	db: SQLiteObject;
}
